package operator

import (
	"fmt"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	nacosv1beta1 "middleware.io/nacos/api/v1beta1"
	myErrors "middleware.io/nacos/pkg/errors"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
	"strconv"
)

var (
	// nacos 端口默认值
	clientPort = NACOS_PORT

	initScript = `array=(%s)
succ = 0

for element in ${array[@]} 
do
  while true
  do
    ping $element -c 1 > /dev/stdout
    if [[ $? -eq 0 ]]; then
      echo $element "all domain ready"
      break
    else
      echo $element "wait for other domain ready"
    fi
    sleep 1
  done
done
sleep 1

echo "init success"`
)

func (e *KindClient) getServicePort(nacos *nacosv1beta1.Nacos) int {
	if nacos.Spec.ApplicationPort != nil {
		clientPort = *nacos.Spec.ApplicationPort
		return clientPort
	}
	return NACOS_PORT
}

func (e *KindClient) buildStatefulSet(nacos *nacosv1beta1.Nacos) *appsv1.StatefulSet {
	// 公共labels
	labels := e.generateLabels(nacos.Name, NACOS, NACOS)
	// 合并自定义标签
	labels = e.MergeLabels(nacos.Labels, labels)

	// 设置默认的 env
	env := append(nacos.Spec.Env, corev1.EnvVar{
		Name:  "PREFER_HOST_MODE",
		Value: "hostname",
	})

	switch nacos.Spec.FunctionMode {
	case "naming":
		env = append(nacos.Spec.Env, corev1.EnvVar{
			Name:  "FUNCTION_MODE",
			Value: "naming",
		})
	case "config":
		env = append(nacos.Spec.Env, corev1.EnvVar{
			Name:  "FUNCTION_MODE",
			Value: "config",
		})
	}

	// 设置认证相关 env
	if nacos.Spec.Certification.Enabled {
		env = append(env, corev1.EnvVar{
			Name:  "NACOS_AUTH_ENABLE",
			Value: strconv.FormatBool(nacos.Spec.Certification.Enabled),
		})

		env = append(env, corev1.EnvVar{
			Name:  "NACOS_AUTH_TOKEN_EXPIRE_SECONDS",
			Value: nacos.Spec.Certification.TokenExpireSeconds,
		})

		env = append(env, corev1.EnvVar{
			Name:  "NACOS_AUTH_TOKEN",
			Value: nacos.Spec.Certification.Token,
		})

		env = append(env, corev1.EnvVar{
			Name:  "NACOS_AUTH_CACHE_ENABLE",
			Value: strconv.FormatBool(nacos.Spec.Certification.CacheEnabled),
		})
	}

	// 设置数据库 env
	if nacos.Spec.Database.TypeDatabase == "embedded" {
		env = append(env, corev1.EnvVar{
			Name:  "EMBEDDED_STORAGE",
			Value: "embedded",
		})
	} else if nacos.Spec.Database.TypeDatabase == "mysql" {
		env = append(env, corev1.EnvVar{
			Name:  "SPRING_DATASOURCE_PLATFORM",
			Value: nacos.Spec.Database.TypeDatabase,
		})

		env = append(env, corev1.EnvVar{
			Name:  "MYSQL_SERVICE_HOST",
			Value: nacos.Spec.Database.MysqlHost,
		})

		env = append(env, corev1.EnvVar{
			Name:  "MYSQL_SERVICE_PORT",
			Value: nacos.Spec.Database.MysqlPort,
		})

		env = append(env, corev1.EnvVar{
			Name:  "MYSQL_SERVICE_DB_NAME",
			Value: nacos.Spec.Database.MysqlDb,
		})

		env = append(env, corev1.EnvVar{
			Name:  "MYSQL_SERVICE_USER",
			Value: nacos.Spec.Database.MysqlUser,
		})

		env = append(env, corev1.EnvVar{
			Name:  "MYSQL_SERVICE_PASSWORD",
			Value: nacos.Spec.Database.MysqlPassword,
		})
	}

	// 启动模式，默认为cluster
	if nacos.Spec.Type == TypeStandAlone {
		env = append(env, corev1.EnvVar{
			Name:  "MODE",
			Value: "standalone",
		})
	} else {
		env = append(env, corev1.EnvVar{
			Name:  "NACOS_REPLICAS",
			Value: strconv.Itoa(int(*nacos.Spec.Replicas)),
		})
	}
	if nacos.Spec.ApplicationPort != nil {
		clientPort = *nacos.Spec.ApplicationPort
		env = append(env, corev1.EnvVar{
			Name:  "NACOS_APPLICATION_PORT",
			Value: strconv.Itoa(*nacos.Spec.ApplicationPort),
		})
	}

	// 创建StatefulSet
	var sta = &appsv1.StatefulSet{
		ObjectMeta: metav1.ObjectMeta{
			Name:        e.generateName(nacos),
			Namespace:   nacos.Namespace,
			Labels:      labels,
			Annotations: nacos.Annotations,
		},
		Spec: appsv1.StatefulSetSpec{
			PodManagementPolicy: appsv1.ParallelPodManagement,
			Replicas:            nacos.Spec.Replicas,
			Selector:            &metav1.LabelSelector{MatchLabels: labels},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: labels,
				},
				Spec: corev1.PodSpec{
					NodeSelector: nacos.Spec.NodeSelector,
					Affinity:     nacos.Spec.Affinity,
					Volumes:      []corev1.Volume{},
					Tolerations:  nacos.Spec.Tolerations,
					Containers: []corev1.Container{
						{
							Name:            nacos.Name + "-server",
							Image:           nacos.Spec.Image,
							ImagePullPolicy: nacos.Spec.ImagePullPolicy,
							Lifecycle: &corev1.Lifecycle{
								PreStop: &corev1.LifecycleHandler{
									Exec: &corev1.ExecAction{
										Command: []string{
											"/bin/sh",
											"-c",
											"rm -rf /home/nacos/data/protocol/raft",
										},
									},
								},
							},
							Ports: []corev1.ContainerPort{
								{
									Name:          "client",
									ContainerPort: int32(clientPort),
									Protocol:      corev1.ProtocolTCP,
								},
								{
									Name:          "rpc",
									ContainerPort: int32(clientPort - 1000),
									Protocol:      corev1.ProtocolTCP,
								},
								{
									Name:          "new-rpc",
									ContainerPort: int32(clientPort + 1000),
									Protocol:      corev1.ProtocolTCP,
								},
							},
							Env:            env,
							LivenessProbe:  nacos.Spec.LivenessProbe,
							ReadinessProbe: nacos.Spec.ReadinessProbe,
							VolumeMounts:   []corev1.VolumeMount{},
							Resources:      nacos.Spec.Resources,
						},
					},
				},
			},
		},
	}

	// 设置 volume 存储
	if nacos.Spec.Volume.Enabled {
		sta.Spec.VolumeClaimTemplates = append(sta.Spec.VolumeClaimTemplates, corev1.PersistentVolumeClaim{
			ObjectMeta: metav1.ObjectMeta{
				Name:   "data",
				Labels: labels,
			},
			Spec: corev1.PersistentVolumeClaimSpec{
				StorageClassName: nacos.Spec.Volume.StorageClass,
				AccessModes:      nacos.Spec.Volume.AccessModes,
				Resources: corev1.ResourceRequirements{
					Requests: nacos.Spec.Volume.Requests,
				},
			},
		})

		// VolumeMounts data
		localVolume := corev1.VolumeMount{
			Name:      "data",
			MountPath: "/home/nacos/data",
		}
		sta.Spec.Template.Spec.Containers[0].VolumeMounts = append(sta.Spec.Template.Spec.Containers[0].VolumeMounts, localVolume)
	}

	// custom config
	if nacos.Spec.Config != "" {
		sta.Spec.Template.Spec.Volumes = append(sta.Spec.Template.Spec.Volumes, corev1.Volume{
			Name: "config",
			VolumeSource: corev1.VolumeSource{
				ConfigMap: &corev1.ConfigMapVolumeSource{
					LocalObjectReference: corev1.LocalObjectReference{Name: e.getCustomConfigmapName(nacos)},
					Items: []corev1.KeyToPath{
						{
							Key:  "custom.properties",
							Path: "custom.properties",
						},
					},
				},
			},
		})
		sta.Spec.Template.Spec.Containers[0].VolumeMounts = append(sta.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
			Name:      "config",
			MountPath: "/home/nacos/init.d/custom.properties",
			SubPath:   "custom.properties",
		})
	}

	//
	myErrors.EnsureNormal(controllerutil.SetControllerReference(nacos, sta, e.scheme))

	return sta
}

func (e *KindClient) buildStatefulSetCluster(nacos *nacosv1beta1.Nacos, ss *appsv1.StatefulSet) *appsv1.StatefulSet {
	domain := "cluster.local"
	// env 中获取 DOMAIN_NAME 变量
	for _, env := range nacos.Spec.Env {
		if env.Name == "DOMAIN_NAME" && env.Value != "" {
			domain = env.Value
		}
	}
	ss.Spec.ServiceName = e.generateHeadlessSvcName(nacos)
	service := ""
	serviceNoPort := ""
	for i := 0; i < int(*nacos.Spec.Replicas); i++ {
		service = fmt.Sprintf("%v%v-%d.%v.%v.%v.%v:%v ", service, e.generateName(nacos), i, e.generateHeadlessSvcName(nacos), nacos.Namespace, "svc", domain, e.getServicePort(nacos))
		serviceNoPort = fmt.Sprintf("%v%v-%d.%v.%v.%v.%v ", serviceNoPort, e.generateName(nacos), i, e.generateHeadlessSvcName(nacos), nacos.Namespace, "svc", domain)
	}
	service = service[0 : len(service)-1]
	env := []corev1.EnvVar{
		{
			Name:  "NACOS_SERVERS",
			Value: service,
		},
	}
	ss.Spec.Template.Spec.Containers[0].Env = append(ss.Spec.Template.Spec.Containers[0].Env, env...)
	// 检查域名解析
	ss.Spec.Template.Spec.Containers[0].Command = []string{"sh", "-c", fmt.Sprintf("%s&&bin/docker-startup.sh", fmt.Sprintf(initScript, serviceNoPort))}

	return ss
}

func (e *KindClient) generateLabels(name, mid, component string) map[string]string {
	return map[string]string{
		"app":        name,
		"middleware": mid,
		"component":  component,
	}
}

func (e *KindClient) generateAnnotation() map[string]string {
	return map[string]string{}
}

// MergeLabels 合并cr中的label 和 固定的label
func (e *KindClient) MergeLabels(allLabels ...map[string]string) map[string]string {
	res := map[string]string{}
	for _, labels := range allLabels {
		if labels != nil {
			for k, v := range labels {
				res[k] = v
			}
		}
	}
	return res
}

func (e *KindClient) generateName(nacos *nacosv1beta1.Nacos) string {
	return nacos.Name
}

func (e *KindClient) generateHeadlessSvcName(nacos *nacosv1beta1.Nacos) string {
	return fmt.Sprintf("%s-headless", nacos.Name)
}

func (e *KindClient) generateClientSvcName(nacos *nacosv1beta1.Nacos) string {
	return fmt.Sprintf("%s-client", nacos.Name)
}

func (e *KindClient) getCustomConfigmapName(nacos *nacosv1beta1.Nacos) string {
	return fmt.Sprintf("%s-custom-config", e.generateName(nacos))
}

// ConfigMap
func (e *KindClient) buildConfigMap(nacos *nacosv1beta1.Nacos) *corev1.ConfigMap {
	labels := e.generateLabels(nacos.Name, NACOS, NACOS)
	labels = e.MergeLabels(nacos.Labels, labels)
	data := make(map[string]string)

	data["custom.properties"] = nacos.Spec.Config
	cm := corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:        e.getCustomConfigmapName(nacos),
			Namespace:   nacos.Namespace,
			Labels:      labels,
			Annotations: nacos.Annotations,
		},
		Data: data,
	}
	myErrors.EnsureNormal(controllerutil.SetControllerReference(nacos, &cm, e.scheme))
	return &cm
}

// Service
func (e *KindClient) buildService(nacos *nacosv1beta1.Nacos) *corev1.Service {
	labels := e.generateLabels(nacos.Name, NACOS, NACOS)
	labels = e.MergeLabels(nacos.Labels, labels)

	annotations := e.MergeLabels(e.generateAnnotation(), nacos.Annotations)

	svc := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:        nacos.Name,
			Namespace:   nacos.Namespace,
			Labels:      labels,
			Annotations: annotations,
		},
		Spec: corev1.ServiceSpec{
			// pod 未就绪是否绑定service
			PublishNotReadyAddresses: false,
			Ports: []corev1.ServicePort{
				{
					Name:     "client",
					Port:     int32(e.getServicePort(nacos)),
					Protocol: "TCP",
				},
				{
					Name:     "rpc",
					Port:     int32(e.getServicePort(nacos) - 1000),
					Protocol: "TCP",
				},
				{
					Name:     "new-rpc",
					Port:     int32(e.getServicePort(nacos) + 1000),
					Protocol: "TCP",
				},
			},
			Selector: labels,
		},
	}
	myErrors.EnsureNormal(controllerutil.SetControllerReference(nacos, svc, e.scheme))
	return svc
}

func (e *KindClient) buildClientService(nacos *nacosv1beta1.Nacos) *corev1.Service {
	labels := e.generateLabels(nacos.Name, NACOS, NACOS)
	labels = e.MergeLabels(nacos.Labels, labels)

	annotations := e.MergeLabels(e.generateAnnotation(), nacos.Annotations)

	svc := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:        e.generateClientSvcName(nacos),
			Namespace:   nacos.Namespace,
			Labels:      labels,
			Annotations: annotations,
		},
		Spec: corev1.ServiceSpec{
			// pod 未就绪是否绑定service
			PublishNotReadyAddresses: true,
			Ports: []corev1.ServicePort{
				{
					Name:     "client",
					Port:     int32(e.getServicePort(nacos)),
					Protocol: "TCP",
				},
				{
					Name:     "new-rpc",
					Port:     int32(e.getServicePort(nacos) + 1000),
					Protocol: "TCP",
				},
			},
			Selector: labels,
		},
	}
	//client-service提供双栈
	var ipf = make([]corev1.IPFamily, 0)
	ipf = append(ipf, corev1.IPv4Protocol)
	// 关闭ipv6
	//ipf = append(ipf, v1.IPv6Protocol)
	svc.Spec.IPFamilies = ipf
	//var ipPli = v1.IPFamilyPolicyPreferDualStack
	var ipPli = corev1.IPFamilyPolicySingleStack
	svc.Spec.IPFamilyPolicy = &ipPli
	myErrors.EnsureNormal(controllerutil.SetControllerReference(nacos, svc, e.scheme))
	return svc
}

func (e *KindClient) buildHeadlessServiceCluster(svc *corev1.Service, nacos *nacosv1beta1.Nacos) *corev1.Service {
	svc.Spec.ClusterIP = corev1.ClusterIPNone
	svc.Name = e.generateHeadlessSvcName(nacos)
	//nacos pod间raft 探测交互走ipv4
	var ipf = make([]corev1.IPFamily, 0)
	ipf = append(ipf, corev1.IPv4Protocol)
	svc.Spec.IPFamilies = ipf
	var ipPli = corev1.IPFamilyPolicySingleStack
	svc.Spec.IPFamilyPolicy = &ipPli
	return svc
}
